Test & Performance

PHPStan ‒ Gamification for developers and teams

Playful debugging in PHP

Roland Golla

Gamification is great, we all know that. What if we could apply the principle to help improve our source code, preferably with just one line in the terminal? Welcome to static code analysis with PHPStan.

PHPStan is more than just a simple PHP linter: The possibilities go beyond the usual finding syntax errors and missing dependencies. The tool manages to analyze code and improve it on this basis. It does not use a rush method – so you don’t have to work on the entire project at once. Users can start small and work their way up, level by level. The levels further you as a developer – and that’s exactly what we want: to develop. PHPStan suggests better solutions and code architecture. For example, it validates array keys that you want to access. If these are not set, you will consequently receive a warning. This way, you can also take care of things that might otherwise disappear in the log files. This results in much better code, which makes you happier as a developer and helps you personally. Good software is sustainable and makes everyone happy.

So let’s start right away and install PHPStan in our current project as a Composer Dependency:

composer require --dev phpstan/phpstan

For the first run on level 5 in Sulu Core, a line in phpstan.neon must be removed:

phpstan analyse src/Sulu/Bundle/PageBundle -c phpstan.neon --level 5

As a result, 126 errors are displayed, as shown in Figure 1.

Fig. 1: The error display of the first pass

The error shown in Figure 2 is something more challenging that can be traced back to the software architecture. So, we found at least one nice error with two commands in the command line – it can be that simple.

Fig. 2: This is a somewhat more serious error

Working together as a team against legacy code

Software quality is a team task. No more legacy code should be written, otherwise, you won’t be able to keep up with the cleanup. Once this is ensured, or at least strongly limited, it can be built upon and progress can be made quickly. Of course in a team, there are very different developers with different skills, but they must have a common denominator. Similar to an athlete’s training, it is important to do this on a daily basis. This is where the different levels of PHPStan excel. Starting at level 1, you can work your way up to level 8. The real challenges await you after level 5.

Cheating is allowed, but it’s less fun

If you really want to, you can exclude certain rules, which makes sense in some cases. A level consists of several rules and rule sets. So, you can start at level 5 and take care of certain code sections later. But cheats are not cool and have no style. Exceptions to code standards are understandable if teams explicitly agree on internal rules and record them in rule sets. In PHPStan, the level achieved is not really earned, so you shouldn’t proudly post on Twitter that you reached level 6. Leveling up is not the primary goal. It’s about improving your code and bringing your skills to a higher level in your daily work – as a team. This is the only way to work together and achieve a common goal. You can also see yourself in a kind of league with other teams. You will feel proud and more satisfied with the work you’ve done.

PHPStan Role Levels [1] at a glance:

  • Level 0: basic checks, unknown classes, unknown functions, unknown methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables
  • Level 1: possibly undefined variables, unknown magic methods and properties on classes with call and get
  • Level 2: unknown methods checked on all expressions (not just $this), validating PHPDocs
  • Level 3: return types, types assigned to properties
  • Level 4: basic dead code checking – always false instanceof and other type checks, dead else branches, unreachable code after return, etc.
  • Level 5: checking types of arguments passed to methods and functions
  • Level 6: report missing typehints
  • Level 7: report partially wrong union types – if you call a method that only exists on some types in a union type, level 7 starts to report that; other possibly incorrect situations
  • Level 8: report calling methods and accessing properties on nullable types

Baseline in TYPO3 project – the future is now

PHPStan offers a great feature with the baseline. A file can be automatically generated, and existing error lines in the code can be excluded. This way, new code can be subjected to quality standards. This is a very good decision strategy. It saves a lot of work and greatly limits the current refactoring task. With luck, it is already manageable. The TYPO3 core has successfully done this thanks to a lot of passion from Alexander Schnitzler (Listing 1). Here, a combination is possible: new code is validated at a much higher level than existing code. The feature is no accident; this method is effective and correct. Without this knowledge, it isn’t possible to start from a certain project complexity (TYPO3 is huge).

phpstan.level4.neon of TYPO3 Core
includes:
  - phpstan.level3.neon
rules:
# - PHPStan\Rules\Arrays\DeadForeachRule
# - PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule
  - PHPStan\Rules\DeadCode\NoopRule
# - PHPStan\Rules\DeadCode\UnreachableStatementRule
  - PHPStan\Rules\Exceptions\DeadCatchRule
# - PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule
  - PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule
  - PHPStan\Rules\Methods\CallToStaticMethodStamentWithoutSideEffectsRule
  - PHPStan\Rules\TooWideTypehints\TooWideArrowFunctionReturnTypehintRule
  - PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule
  - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule
conditionalTags:
  PHPStan\Rules\Variables\IssetRule:
	phpstan.rules.rule: %featureToggles.nullCoalesce%
  PHPStan\Rules\Variables\NullCoalesceRule:
	phpstan.rules.rule: %featureToggles.nullCoalesce%
services:
  -
	class: PHPStan\Rules\Classes\ImpossibleInstanceOfRule
	arguments:
  	checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof%
  	treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
	tags:
  	- phpstan.rules.rule

From my personal experience, I can confirm that a baseline is needed in projects that have several full-time developers. All other projects or sub-projects such as extensions, plug-ins, web services, etc. can be worked through and upgraded level by level without a baseline. There is no reason to be scared – start at level 1 with one Composer command. This can also be easily introduced in your GitLab pipeline as a quality gate. Starting simple is the best way.

As an example, we can create a baseline file [2] using the Command Line:

vendor/bin/phpstan analyze --generate-baseline

Corresponding example code can be seen in Listing 2.

parameters:
  ignoreErrors:
	-
  	message: "#^Only numeric types are allowed in pre\\-decrement, bool\\|float\\|int\\|string\\|null given\\.$#"
  	count: 1
  	path: src/Analyser/Scope.php
	-
  	message: "#^Anonymous function has an unused use \\$container\\.$#"
  	count: 2
  	path: src/Command/CommandHelper.php

Contributing to open source – give something back and be happier

Personally, I have made my greatest progress as a developer by contributing to open source projects. Besides meeting nice people from the community, you’ll also get to know many practical tools and learn a lot of basics. We all use open source projects, and are happy about updates, patches, and releases. If you look at how many projects are in our Composer file, it is an impressive self-service store. So it can’t hurt to give something back to the community every now and then to appreciate important work. It also feels great to contribute a real line of code to a world-renowned open source software and it shows up by name in the Git log. You don’t have to fix complete bugs or implement new features. Code refactoring is an important task and with PHPStan, it’s possible to contribute code improvements. This can be done practically in all PHP open source projects, even if they do not rely on PHPStan. First and foremost, it is about improving code, as I regularly do for the Sulu CMS.

You can’t do it without a pipeline and continuous integration

Automatic quality gates, such as static code analysis and tests, must quickly form a true gate. If they are not green, code is not allowed into the production code (formerly called “master”). If they are only executed locally or even only displayed in color in the IDE, that’s nice – but that’s all it is. Either you work with a real code bouncer, or continue as before. Here, PHPStan is ingenious as a static code analysis that can be unleashed on current code without a fully functional environment. Therefore, it is advisable to follow the prescribed path, bring level 0 or level 1 to green, and then run a pipeline with a runner in GitLab. For this, a .gitlab-ci.yml is also available with all Dockerfiles and is open source on GitHub [3]. The never-code-alone page is available as an example of best practices as a PHP CMS project with the Sulu CMS.

Combination with Captain Hook – an unbeatable set-up

Pipelines are good. But the faster bugs are found, the better. So it only makes sense that PHPStorm includes a plug-in for PHPStan and will implement more quality assurance tools in the IDE in the future. Just as importantly, bugs can no longer be committed and brought into the Git repository. This is where Git Hooks are the answer. A CaptainHook configuration for PHP files can be seen in Listing 3 [4].

"pre-commit": {
  "enabled": true,
  "actions": [
	{
  	"action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting",
  	"options": [],
  	"conditions": []
	},
	{
  	"action": "vendor/bin/phpcs",
  	"options": [],
  	"conditions": []
	},
	{
  	"action": "vendor/bin/phpstan analyse src -c phpstan.neon",
  	"options": [],
  	"conditions": []
	},
	{
  	"action": "vendor/bin/xmllint config -v",
  	"options": [],
  	"conditions": []
	}
  ]
}

There’s more than PHPStan – static code analysis is great

One note in advance: There are also commercial tools that can find security vulnerabilities in code. A look into the world of static code analysis is definitely worthwhile. As can be seen in the CaptainHook configuration example above, four static code analyses are used locally before the commit. Here, there is a useful dump finder that validates my Twig files again. It’s worth taking a look, even if it’s not our current topic at hand.

The important thing is: I underestimated static code analysis and relied on it significantly too late. As far as I was aware, there was only a very simple linter. They were not needed at all without syntax errors.

Thanks to today’s IDEs, syntax errors are very unlikely, whether in Visual Studio Code or PHPStan. I would never have thought that tools, and PHPStan in particular, could permanently enhance my code and be so much fun at the same time. CaptainHook and PHPStan formed the beginning of a wonderful journey towards better code, new knowledge, and a lot of fun as a tool setup. And that’s what people are looking for right now.

Conclusion: Creative work can’t be done without fun

Legacy code makes you sick. It’s another topic, but quality gates and tools are not used in the end of applications. Internally, programmers always tend to have high expectations and are perfectionists. If these expectations cannot be met, a devaluing counter-reaction often follows. In a world between 0 and 1, there are rarely half measures. That is simply not true. Even in code, better is simply better. Thanks to its levels, PHPStan can be used appropriately, easily, and starting now. Now, teams can level themselves up. Additionally, new quality requirements offer the opportunity to bring existing open source projects to a better level and give something back to the community. So, friends, just get started – it’s worth it.

 

Links & Literature

[1] https://phpstan.org/user-guide/rule-levels

[2] https://phpstan.org/user-guide/baseline

[3] https://github.com/nevercodealone/cms-symfony-sulu/blob/master/.gitlab-ci.yml

[4] https://github.com/nevercodealone/cms-symfony-sulu/blob/master/captainhook.json

Top Articles About Test & Performance

Stay tuned!

Register for our newsletter

Behind the Tracks of IPC

PHP Core
Best practices & applications

General Web Development
Broader web development topics

Test & Performance
Software testing and performance improvements

Agile & People
Getting agile right is so important

Software Architecture
All about PHP frameworks, concepts &
environments

DevOps & Deployment
Learn about DevOps and transform your development pipeline

Content Management Systems
Sessions on content management systems

#slideless (pure coding)
See how technology really works

Web Security
All about
web security

PUSH YOUR CODE FURTHER